package com.gitee.qdbp.jdbc.result;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.jdbc.support.JdbcUtils;
import com.gitee.qdbp.able.jdbc.condition.TableJoin;
import com.gitee.qdbp.able.jdbc.condition.TableJoin.TableItem;
import com.gitee.qdbp.jdbc.model.AllFieldColumn;
import com.gitee.qdbp.jdbc.model.FieldColumns;
import com.gitee.qdbp.jdbc.model.FieldScene;
import com.gitee.qdbp.jdbc.model.TablesFieldColumn;
import com.gitee.qdbp.jdbc.plugins.DbPluginHelper;
import com.gitee.qdbp.tools.utils.VerifyTools;

/**
 * 多个表关联的结果集行数据保存到JavaBean对应子对象的转换类<br>
 * 对于SYS_USER,SYS_USER_ROLE,SYS_ROLE这样的关联查询<br>
 * 新建一个结果类, 有SysUser user, SysUserRole userRole, SysRole role三个字段(子对象), 分别保存来自三个表的查询结果!<br>
 * 如果查询结果不需要关注SYS_USER_ROLE这个关联表, 也可以建SysUser user, SysRole role两个字段(子对象)的类来保存查询结果<br>
 * 实现思路:<br>
 * TableJoin有参数resultField, 用于指定表数据保存至结果类的哪个字段(子对象)<br>
 * 生成的查询语句的查询字段, 对于重名字段加上表别名作为前缀, 生成列别名, <br>
 * 如U__ID, U__REMARK, UR__ID, UR__REMARK, R__ID, R__REMARK<br>
 * 查询结果根据列别名找到字段名和表别名; 再根据表别名找到resultField, 根据字段名填充数据<br>
 *
 * @author zhaohuihua
 * @version 190617
 */
public class TablesRowToProperyMapper<T> implements RowToBeanMapper<T>, DbPluginHelper.Aware {

    private final TableJoin tables;
    private final Class<T> mappedClass;
    /** 插件容器 **/
    private DbPluginHelper plugins;

    public TablesRowToProperyMapper(TableJoin tables, Class<T> mappedClass) {
        this.tables = tables;
        this.mappedClass = mappedClass;
    }

    protected final TableJoin getTables() {
        return this.tables;
    }

    protected final Class<T> getMappedClass() {
        return this.mappedClass;
    }

    @Override
    public void setPlugins(DbPluginHelper plugins) {
        this.plugins = plugins;
    }

    @Override
    public T mapRow(ResultSet rs, int rowNum) throws SQLException {
        // 将ResultSet的行数据转换为Map
        Map<String, Object> map = rowToMap(rs, rowNum);
        // 利用工具类进行Map到JavaBean的转换
        return plugins.getMapToBeanConverter().convert(map, getMappedClass());
    }

    protected Map<String, Object> rowToMap(ResultSet rs, int rowNum) throws SQLException {
        // 1. 获取列名与字段名的对应关系
        // 获取结果类的所有字段信息
        AllFieldColumn<TablesFieldColumn> all = plugins.parseAllFieldColumns(getTables());
        FieldColumns<TablesFieldColumn> fieldColumns = all.filter(FieldScene.RESULT);

        Map<String, Object> result = new LinkedHashMap<>(); // 结果容器
        Map<String, Map<String, Object>> subs = new HashMap<>(); // 子对象容器
        // 2. 根据TableJoin的resultField生成子Map对象
        String majorField = tables.getMajor().getResultField();
        if (VerifyTools.isNotBlank(majorField) && !majorField.equals("this")) {
            subs.put(majorField, new LinkedHashMap<String, Object>());
        }
        for (TableItem item : tables.getJoins()) {
            String itemField = item.getResultField();
            if (VerifyTools.isNotBlank(itemField) && !itemField.equals("this")) {
                subs.put(itemField, new LinkedHashMap<String, Object>());
            }
        }
        if (!subs.isEmpty()) {
            result.putAll(subs);
        }

        // 3. 根据列别名查找字段信息, 再找到resultField, 根据字段名填充数据
        ResultSetMetaData rsmd = rs.getMetaData();
        int columnCount = rsmd.getColumnCount();
        for (int i = 1; i <= columnCount; i++) {
            // 根据列别名查找字段信息
            String columnAlias = JdbcUtils.lookupColumnName(rsmd, i);
            TablesFieldColumn field = fieldColumns.findByColumnAlias(columnAlias);
            if (field == null) {
                continue; // 结果集的列在结果容器类中找不到对应的字段
            }
            // 获取resultField
            String resultField = field.getResultField();
            if (VerifyTools.isBlank(resultField)) {
                continue; // 如果没有指定resultField, 说明不需要保存结果
            }

            // 将字段名和字段值根据resultField填充至对应的子对象中
            // 如果resultField=this直接填充到主容器, 否则填充到指定的子容器
            Map<String, Object> maps = resultField.equals("this") ? result : subs.get(resultField);
            fillColumnValue(rs, i, field, maps);
        }
        return result;
    }

    protected void fillColumnValue(ResultSet rs, int index, TablesFieldColumn field, Map<String, Object> maps)
            throws SQLException {
        // 根据字段类型获取字段值
        String fieldName = field.getFieldName();
        Class<?> fieldType = field.getJavaType();
        Object value = getColumnValue(rs, index, fieldType);
        maps.put(fieldName, value);
    }

    protected Object getColumnValue(ResultSet rs, int index, Class<?> fieldType) throws SQLException {
        return JdbcUtils.getResultSetValue(rs, index, fieldType);
    }

    /**
     * TablesRowToProperyMapper的工厂类
     *
     * @author zhaohuihua
     * @version 20210306
     */
    public static class Factory implements FactoryOfTables, DbPluginHelper.Aware {

        /** 插件容器 **/
        private DbPluginHelper plugins;

        /** 插件容器 **/
        @Override
        public void setPlugins(DbPluginHelper plugins) {
            this.plugins = plugins;
        }

        @Override
        public <T> RowToBeanMapper<T> getRowToBeanMapper(TableJoin tables, Class<T> mappedClass) {
            TablesRowToProperyMapper<T> mapper = new TablesRowToProperyMapper<>(tables, mappedClass);
            mapper.setPlugins(plugins);
            return mapper;
        }
    }
}
